package com.iceberg.adgt.io;

import com.iceberg.adgt.common.DomainException;
import com.iceberg.adgt.domain.DomainFacade;
import com.iceberg.adgt.domain.DomainFacadeImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.File;
import java.io.IOException;

/**
 * @author David
 */
public final class XMLReader implements Reader {

	private static final Logger LOG = LoggerFactory.getLogger(XMLReader.class);
	private static final String INVALID_VALUE_FOR = "Invalid value for ";
	private static final String NAME = "name";
	private static final String AUTHOR = "author";
	private static final String VALUE = "value";
	private static final String LAYER = "layer";
	private DomainFacade facade;
	private File file;
	private Document dom;

	public XMLReader() {
		facade = DomainFacadeImpl.getInstance();
	}

	@Override
	public void openArchitectureFile(File file) {
		this.file = file;
		getDOM();
		readAndSetArchitectureName();
		readAndSetArchitectureAuthor();
		readLayers();
		readAllowedLayers();
	}

	@Override
	public void openStructureFile(File file) {
		this.file = file;
		getDOM();
		readAndSetStructureName();
		readAndSetStructureAuthor();
		readUnits();
	}

	private void getDOM() {
		try {
			DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
			DocumentBuilder db = dbf.newDocumentBuilder();
			dom = db.parse(file);
		} catch (ParserConfigurationException ex) {
			LOG.error("Mis-configuration of the document builder", ex);
		} catch (SAXException ex) {
			LOG.error("Invalid xml", ex);
		} catch (IOException ex) {
			LOG.error("Unable to open file '{}'", file.getAbsolutePath(), ex);
		}
	}

	private void readAndSetArchitectureName() {
		NodeList names = dom.getElementsByTagName(NAME);

		if (names.getLength() == 1) {
			String value = ((Element) names.item(0)).getAttribute(VALUE);
			try {
				facade.setArchitectureName(value);
			} catch (DomainException ex) {
				LOG.error(INVALID_VALUE_FOR + NAME, ex);
			}
		}
	}

	private void readAndSetArchitectureAuthor() {
		NodeList names = dom.getElementsByTagName(AUTHOR);

		if (names.getLength() == 1) {
			String value = ((Element) names.item(0)).getAttribute(VALUE);
			try {
				facade.setArchitectureAuthor(value);
			} catch (DomainException ex) {
				LOG.error(INVALID_VALUE_FOR + AUTHOR, ex);
			}
		}
	}

	private void readAndSetStructureName() {
		NodeList names = dom.getElementsByTagName(NAME);

		if (names.getLength() == 1) {
			String value = ((Element) names.item(0)).getAttribute(VALUE);
			try {
				facade.setStructureName(value);
			} catch (DomainException ex) {
				LOG.error(INVALID_VALUE_FOR + NAME, ex);
			}
		}
	}

	private void readAndSetStructureAuthor() {
		NodeList names = dom.getElementsByTagName(AUTHOR);

		if (names.getLength() == 1) {
			String value = ((Element) names.item(0)).getAttribute(VALUE);
			try {
				facade.setStructureAuthor(value);
			} catch (DomainException ex) {
				LOG.error(INVALID_VALUE_FOR + AUTHOR, ex);
			}
		}
	}

	private void readLayers() {
		NodeList children = dom.getDocumentElement().getChildNodes();

		for (int s = 0; s < children.getLength(); s++) {
			if (children.item(s).getNodeName().equals(LAYER)) {
				String layerName = children.item(s).getAttributes().getNamedItem(NAME).getNodeValue();
				String layerNumber = children.item(s).getAttributes().getNamedItem("number").getNodeValue();
				try {
					facade.addLayer(layerName, Integer.parseInt(layerNumber));
				} catch (DomainException ex) {
					LOG.error("Invalid values for layer", ex);
				}
			}
		}
	}

	private void readUnits() {
		NodeList layers = dom.getElementsByTagName("layer");

		for (int s = 0; s < layers.getLength(); s++) {
			Node layer = layers.item(s);
			NodeList children = layer.getChildNodes();

			for (int s2 = 0; s2 < children.getLength(); s2++) {
				Node child = children.item(s2);
				if (child.getNodeName().equals("package")) {
					try {
						facade.addUnitToLayer(child.getAttributes().getNamedItem(NAME).getNodeValue(), "PACKAGE", layer.getAttributes().getNamedItem(NAME).getNodeValue());
					} catch (DomainException ex) {
						LOG.error(INVALID_VALUE_FOR + "adding a package to layer", ex);
					}
				} else if (child.getNodeName().equals("class")) {
					try {
						facade.addUnitToLayer(child.getAttributes().getNamedItem(NAME).getNodeValue(), "UNIT", layer.getAttributes().getNamedItem(NAME).getNodeValue());
					} catch (DomainException ex) {
						LOG.error(INVALID_VALUE_FOR + "adding a unit to layer");
					}

				}
			}
		}
	}

	private void readAllowedLayers() {
		NodeList allowedToAccessList = dom.getElementsByTagName("allowedToAccess");

		for (int i = 0; i < allowedToAccessList.getLength(); i++) {
			Node allowedToAccess = allowedToAccessList.item(i);
			String parentLayer = allowedToAccess.getParentNode().getAttributes().getNamedItem(NAME).getNodeValue();
			NodeList accessLayerList = allowedToAccess.getChildNodes();

			for (int s = 0; s < accessLayerList.getLength(); s++) {
				Node accessLayer = accessLayerList.item(s);
				if (accessLayer.getNodeName().equals("accessLayer")) {
					try {
						facade.addAllowedLayerToLayer(parentLayer, accessLayer.getAttributes().getNamedItem(NAME).getNodeValue());
					} catch (DomainException ex) {
						LOG.error(INVALID_VALUE_FOR + "adding a allowed layer to another layer", ex);
					}
				}
			}
		}
	}
}